package com.gaussian.property.app.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.NumberUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.gaussian.property.app.vo.dashboard.*;
import com.gaussian.property.app.vo.dashboard.ProjectMonitor;
import com.gaussian.property.module.biz.domain.ProjectMonitorIndicatorConfig;
import com.gaussian.property.module.biz.entity.*;
import com.gaussian.property.module.biz.service.*;
import com.songqi.common.core.exception.CustomException;
import com.songqi.common.core.utils.BeanCopyUtil;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.*;
import java.util.stream.Collectors;

import static cn.hutool.core.date.DatePattern.*;
import static com.gaussian.property.module.biz.constant.MonitorIndicatorConstants.*;

/**
 * @description:
 * @author: songqi(13776346982)
 * @date: 2024/10/3 17:58
 */
@Service
public class DashboardService {

    @Resource
    private IDailyStatisticsService iDailyStatisticsService;
    @Resource
    private IMonthlyStatisticsService iMonthlyStatisticsService;
    @Resource
    private ICrewDailyStatisticsService iCrewDailyStatisticsService;
    @Resource
    private ICrewMonthlyStatisticsService iCrewMonthlyStatisticsService;
    @Resource
    private ISpaceDailyStatisticsService iSpaceDailyStatisticsService;
    @Resource
    private IRobotDailyStatisticsService iRobotDailyStatisticsService;
    @Resource
    private IProjectMonitorIndicatorService iProjectMonitorIndicatorService;
    @Resource
    private IUserReadProjectAlertService iUserReadProjectAlertService;
    @Resource
    private IProjectMonitorService iProjectMonitorService;


    private static final String FIRST_DAY = "firstDay";
    private static final String LAST_DAY = "lastDay";




    /**
     * 今日监控警告弹框
     * @param appUserId
     * @param projectId
     * @param dateStr
     * @return
     */
    public List<ProjectMonitor> getProjectMonitor(String appUserId, String projectId, String dateStr) {
        // 判断当前用户是否有过弹出框
        if(checkAppUserReadAlert(appUserId, projectId, dateStr)){
            return Collections.emptyList();
        }
        List<ProjectModuleMonitorAlert> projectAllModuleMonitorAlert = getProjectAllModuleMonitorAlert(projectId, dateStr);
        if(CollUtil.isEmpty(projectAllModuleMonitorAlert)){
            return Collections.emptyList();
        }
        List<ProjectMonitor> projectMonitorList = new ArrayList<>();
        for(ProjectModuleMonitorAlert projectModuleMonitorAlert : projectAllModuleMonitorAlert){
            ProjectMonitor projectMonitor = new ProjectMonitor();
            projectMonitor.setModuleCode(projectModuleMonitorAlert.getModuleCode());
            projectMonitor.setModuleName(projectModuleMonitorAlert.getModuleName());
            projectMonitor.setSummaryTips(getMostImportAlertMsg(projectModuleMonitorAlert.getAlertMsgList()));
            projectMonitorList.add(projectMonitor);
        }
        saveUserReadAlert(appUserId, projectId, dateStr);
        return projectMonitorList;
    }

    /**
     * 报表监控建议列表
     * @param projectId
     * @param dateStr
     * @return
     */
    public List<ProjectModuleReportMonitor> reportMonitorSuggest(String projectId, String dateStr) {

        List<ProjectModuleMonitorAlert> projectAllModuleMonitorAlert = getProjectAllModuleMonitorAlert(projectId, dateStr);
        if(CollUtil.isEmpty(projectAllModuleMonitorAlert)){
            return Collections.emptyList();
        }
        List<ProjectModuleReportMonitor> projectMonitorList = new ArrayList<>();
        for(ProjectModuleMonitorAlert projectModuleMonitorAlert : projectAllModuleMonitorAlert){
            ProjectModuleReportMonitor projectMonitor = new ProjectModuleReportMonitor();
            projectMonitor.setModuleCode(projectModuleMonitorAlert.getModuleCode());
            projectMonitor.setModuleName(projectModuleMonitorAlert.getModuleName());
            projectMonitor.setSuggestTipsList(getSugguestTipsList(projectModuleMonitorAlert.getAlertMsgList()));
            projectMonitorList.add(projectMonitor);
        }
        return projectMonitorList;
    }


    private List<String> getSugguestTipsList(List<ProjectModuleMonitorAlert.AlertMsg> alertMsgList) {
        List<String> suggestTipsList = new ArrayList<>();
        if(CollUtil.isNotEmpty(alertMsgList)){
            for(ProjectModuleMonitorAlert.AlertMsg alertMsg : alertMsgList){
                if(CharSequenceUtil.isNotBlank(alertMsg.getSuggestTips())){
                    suggestTipsList.add(alertMsg.getSuggestTips());
                }
            }
        }
        return suggestTipsList;
    }

    private String getMostImportAlertMsg(List<ProjectModuleMonitorAlert.AlertMsg> alertMsgList) {
        if(CollUtil.isEmpty(alertMsgList)){
            return null;
        }
        for(ProjectModuleMonitorAlert.AlertMsg alertMsg : alertMsgList){
            if(alertMsg != null && CharSequenceUtil.isNotBlank(alertMsg.getSummaryTips())){
                return alertMsg.getSummaryTips();
            }
        }
        return null;
    }

    private void saveUserReadAlert(String appUserId, String projectId, String dateStr) {
        UserReadProjectAlert alert = new UserReadProjectAlert();
        alert.setAppUserId(appUserId);
        alert.setProjectId(projectId);
        alert.setStatisticsDate(dateStr);
        iUserReadProjectAlertService.save(alert);
    }

    private boolean checkAppUserReadAlert(String appUserId, String projectId, String dateStr) {
        LambdaQueryWrapper<UserReadProjectAlert> queryWrapper = new LambdaQueryWrapper<UserReadProjectAlert>()
                .eq(UserReadProjectAlert::getAppUserId, appUserId)
                .eq(UserReadProjectAlert::getProjectId, projectId)
                .eq(UserReadProjectAlert::getStatisticsDate, dateStr);
        return iUserReadProjectAlertService.count(queryWrapper) > 0;
    }

    /**
     * 项目运行监控提示信息
     * @param projectId
     * @param dateStr
     * @return
     */
    private List<ProjectModuleMonitorAlert> getProjectAllModuleMonitorAlert(String projectId, String dateStr){
        List<ProjectModuleMonitorAlert> projectModuleMonitorAlertList = new ArrayList<>();
        if(! projectIsEnableMonitor(projectId)){
            return projectModuleMonitorAlertList;
        }
        ProjectModuleMonitorAlert attendanceMonitorAlert = getProjectModuleMonitorAlert(projectId, dateStr, ATTENDANCE);
        if(attendanceMonitorAlert != null) {
            projectModuleMonitorAlertList.add(attendanceMonitorAlert);
        }
        ProjectModuleMonitorAlert robotMonitorAlert = getProjectModuleMonitorAlert(projectId, dateStr, ROBOT);
        if(robotMonitorAlert != null) {
            projectModuleMonitorAlertList.add(robotMonitorAlert);
        }
        ProjectModuleMonitorAlert jobMonitorAlert = getProjectModuleMonitorAlert(projectId, dateStr, JOB);
        if(jobMonitorAlert != null) {
            projectModuleMonitorAlertList.add(jobMonitorAlert);
        }
        ProjectModuleMonitorAlert crewMonitorAlert = getProjectModuleMonitorAlert(projectId, dateStr, CREW);
        if(crewMonitorAlert != null) {
            projectModuleMonitorAlertList.add(crewMonitorAlert);
        }
        return projectModuleMonitorAlertList;
    }


    /**
     * 判断项目是否启用监控
     * @param projectId
     * @return
     */
    private boolean projectIsEnableMonitor(String projectId) {
        return iProjectMonitorService.isProjectEnableMonitor(projectId);
    }


    /**
     * 查询趋势看板数据
     * @param projectId
     * @param yearMonth
     * @return
     */
    public List<Trend> getTrends(String projectId, String yearMonth) {
        Map<String, LocalDate> dateRange = getDateRange(yearMonth);
        LocalDate firstDay = dateRange.get(FIRST_DAY);
        LocalDate lastDay = dateRange.get(LAST_DAY);
        return queryProjectData(projectId, firstDay, lastDay, false);
    }

    /**
     * 今日总扣款：人员考勤扣款、作业品质扣款、机器人扣款
     * ● total_slow_down_deduct_amount	总怠工扣款金额
     * ● total_gather_pos_deduct_amount	总聚岗扣款金额
     * ● total_leave_pos_deduct_amount	总脱岗扣款金额
     * ● total_gad_pos_deduct_amount	总串岗扣款金额
     * ● total_leave_site_deduct_amount 总离场扣款金额
     * ● total_vacant_pos_deduct_amount	总缺岗扣款金额
     * ● total_overage_deduct_amount	总超龄扣款金额
     * ● total_risk_area_deduct_amount	总风险区扣款金额
     * ● total_inspect_cover_deduct_amount	总巡检覆盖扣款金额
     * @param projectId
     * @param dateStr
     * @return
     */
    public Double getDailyDeduction(String projectId, String dateStr) {
        DailyStatistics dailyStatistics = getDailyStatistics(projectId, dateStr);
        if (dailyStatistics == null) {
            return 0.0d;
        }
        return NumberUtil.add(
                getDoubleValue(dailyStatistics.getTotalSlowDownDeductAmount()),
                getDoubleValue(dailyStatistics.getTotalGatherPosDeductAmount()),
                getDoubleValue(dailyStatistics.getTotalLeavePosDeductAmount()),
                getDoubleValue(dailyStatistics.getTotalGadPosDeductAmount()),
                getDoubleValue(dailyStatistics.getTotalLeaveSiteDeductAmount()),
                getDoubleValue(dailyStatistics.getTotalVacantPosDeductAmount()),
                getDoubleValue(dailyStatistics.getTotalOverageDeductAmount()),
                getDoubleValue(dailyStatistics.getTotalRiskAreaDeductAmount()),
                getDoubleValue(dailyStatistics.getTotalInspectCoverDeductAmount()))
                .doubleValue();
    }


    /**
     * 月度扣款统计
     * @param projectId
     * @param month
     * @return
     */
    public MonthlyDeduction getMonthlyDeduction(String projectId, String month) {
        MonthlyStatistics monthlyStatistics = getMonthlyStatistics(projectId, month);
        if (monthlyStatistics == null) {
            return null;
        }
        Double monthAttendance = getMonthlyAttendanceDeduction(monthlyStatistics);
        Double monthQuality = getMonthlyQualityDeduction(monthlyStatistics);
        Double monthTotal = NumberUtil.add(monthAttendance, monthQuality);
        MonthlyDeduction monthDeduction = new MonthlyDeduction();
        monthDeduction.setTotal(monthTotal);
        monthDeduction.setAttendance(monthAttendance);
        monthDeduction.setQuality(monthQuality);
        return monthDeduction;
    }

    /**
     * 每日扣款明细。对应扣款明细的今日数据和今日扣款两列
     * @param projectId
     * @param dateStr
     * @return
     */
    public DeductionDetail getDailyDeductionDetail(String projectId, String dateStr) {
        DailyStatistics dailyStatistics = getDailyStatistics(projectId, dateStr);
        if(dailyStatistics == null) {
            return null;
        }
        DeductionDetail deductionDetail = new DeductionDetail();

        // 人员考勤数据
        DeductionDetail.AttendanceIndicator attendanceDataDetail = getDailyAttendanceDataDetail(dailyStatistics);
        deductionDetail.setAttendanceDataDetail(attendanceDataDetail);
        // 人员考勤扣款明细
        DeductionDetail.AttendanceIndicator attendanceDeductionDetail = getDailyAttendanceDeductionDetail(dailyStatistics);
        deductionDetail.setAttendanceDeductionDetail(attendanceDeductionDetail);
        // 人员考勤扣款总计
        deductionDetail.setAttendanceDeduction(getDailyAttendanceDeduction(dailyStatistics));

        // 作业品质数据
        DeductionDetail.QualityIndicator qualityDataDetail = getDailyQualityDataDetail(dailyStatistics);
        deductionDetail.setQualityDataDetail(qualityDataDetail);
        // 作业品质扣款明细
        DeductionDetail.QualityIndicator qualityDeductionDetail = getDailyQualityDeductionDetail(dailyStatistics);
        deductionDetail.setQualityDeductionDetail(qualityDeductionDetail);
        // 作业品质扣款总计
        deductionDetail.setQualityDeduction(getDailyQualityDeduction(dailyStatistics));

        return deductionDetail;
    }




    /**
     * 本月扣款明细。对应扣款明细的本月数据和本月扣款两列
     * @param projectId
     * @param month
     * @return
     */
    public DeductionDetail getMonthlyDeductionDetail(String projectId, String month) {
        MonthlyStatistics monthlyStatistics = getMonthlyStatistics(projectId, month);
        if (monthlyStatistics == null){
            return null;
        }
        DeductionDetail deductionDetail = new DeductionDetail();

        // 人员考勤数据
        DeductionDetail.AttendanceIndicator attendanceDataDetail = getMonthlyAttendanceDataDetail(monthlyStatistics);
        deductionDetail.setAttendanceDataDetail(attendanceDataDetail);
        // 人员考勤扣款明细
        DeductionDetail.AttendanceIndicator attendanceDeductionDetail = getMonthlyAttendanceDeductionDetail(monthlyStatistics);
        deductionDetail.setAttendanceDeductionDetail(attendanceDeductionDetail);
        // 人员考勤扣款总计
        deductionDetail.setAttendanceDeduction(getMonthlyAttendanceDeduction(monthlyStatistics));

        // 作业品质数据
        DeductionDetail.QualityIndicator qualityDataDetail = getMonthlyQualityDataDetail(monthlyStatistics);
        deductionDetail.setQualityDataDetail(qualityDataDetail);
        // 作业品质扣款明细
        DeductionDetail.QualityIndicator qualityDeductionDetail = getMonthlyQualityDeductionDetail(monthlyStatistics);
        deductionDetail.setQualityDeductionDetail(qualityDeductionDetail);
        // 作业品质扣款总计
        deductionDetail.setQualityDeduction(getMonthlyQualityDeduction(monthlyStatistics));
        return deductionDetail;
    }



    /**
     * 每日考勤人数统计
     * @param projectId
     * @param date
     * @return
     */
    public CrewAttendanceCount getDailyCrewAttendanceCount(String projectId, String date) {
        DailyStatistics dailyStatistics = getDailyStatistics(projectId, date);
        if(dailyStatistics == null) {
            return null;
        }
        return BeanCopyUtil.copyProperties(dailyStatistics, CrewAttendanceCount.class);
    }

    /**
     * 月度考勤人数统计
     * @param projectId
     * @param month
     * @return
     */
    public CrewAttendanceCount getMonthlyCrewAttendanceCount(String projectId, String month) {
        MonthlyStatistics monthlyStatistics = getMonthlyStatistics(projectId, month);
        if(monthlyStatistics == null) {
            return null;
        }
        return BeanCopyUtil.copyProperties(monthlyStatistics, CrewAttendanceCount.class);
    }

    /**
     * 月度考勤人数明细
     * @param projectId
     * @param yearMonth
     * @return
     */
    public List<DailyCrewAttendance> getMonthlyCrewAttendanceDetail(String projectId, String yearMonth) {
        List<DailyCrewAttendance> crewAttendanceDetails = new ArrayList<>();
        Map<String, LocalDate> dateRange = getDateRange(yearMonth);
        LocalDate firstDay = dateRange.get(FIRST_DAY);
        LocalDate lastDay = dateRange.get(LAST_DAY);
        List<DailyStatistics> list = iDailyStatisticsService.getDailyStatisticsList(projectId, firstDay, lastDay);
        if(CollUtil.isNotEmpty(list)) {
            // 排序
            LinkedHashMap<String, List<DailyStatistics>> collectMap = list.stream()
                    .sorted(Comparator.comparing(DailyStatistics::getDate).reversed())
                    .collect(Collectors.groupingBy(DailyStatistics::getStatisticsDate, LinkedHashMap::new, Collectors.toList()));
            for (Map.Entry<String, List<DailyStatistics>> entry : collectMap.entrySet()) {
                List<DailyStatistics> dailyStatisticsList = entry.getValue();
                DailyStatistics dailyStatistics = dailyStatisticsList.get(0);
                crewAttendanceDetails.add(BeanCopyUtil.copyProperties(dailyStatistics, DailyCrewAttendance.class));
            }
        }
        return crewAttendanceDetails;
    }


    /**
     * 每日员工行为统计
     * @param projectId
     * @param date
     * @return
     */
    public CrewBehaviorCount getDailyCrewBehaviorCount(String projectId, String date) {
        DailyStatistics dailyStatistics = getDailyStatistics(projectId, date);
        if(dailyStatistics == null) {
            return null;
        }
        return BeanCopyUtil.copyProperties(dailyStatistics, CrewBehaviorCount.class);
    }

    /**
     * 每月员工行为统计
     * @param projectId
     * @param month
     * @return
     */
    public CrewBehaviorCount getMonthlyCrewBehaviorCount(String projectId, String month) {
        MonthlyStatistics monthlyStatistics = getMonthlyStatistics(projectId, month);
        if(monthlyStatistics == null) {
            return null;
        }
        return BeanCopyUtil.copyProperties(monthlyStatistics, CrewBehaviorCount.class);
    }

    /**
     * 按日查询员工行为明细
     * @param projectId
     * @param date
     * @return
     */
    public List<CrewBehaviorDetail> getDailyCrewBehaviorDetail(String projectId, String date) {
        LambdaQueryWrapper<CrewDailyStatistics> queryWrapper = new LambdaQueryWrapper<CrewDailyStatistics>()
                .eq(CrewDailyStatistics::getItemId, projectId)
                .eq(CrewDailyStatistics::getStatisticsDate, date)
                .and(wrapper ->
                        wrapper.gt(CrewDailyStatistics::getLeavePosDura, 0).or()
                        .gt(CrewDailyStatistics::getSlowDownDura, 0).or()
                        .gt(CrewDailyStatistics::getLeaveSiteDura, 0).or()
                        .gt(CrewDailyStatistics::getGadPosDura, 0).or()
                        .gt(CrewDailyStatistics::getGatherPosDura, 0)
                );
        List<CrewDailyStatistics> list = iCrewDailyStatisticsService.list(queryWrapper);
        return BeanCopyUtil.copyList(list, CrewBehaviorDetail.class);
    }

    /**
     * 按月查询员工行为明细
     * @param projectId
     * @param month
     * @return
     */
    public List<CrewBehaviorDetail> getMonthlyCrewBehaviorDetail(String projectId, String month) {
        LambdaQueryWrapper<CrewMonthlyStatistics> queryWrapper = new LambdaQueryWrapper<CrewMonthlyStatistics>()
                .eq(CrewMonthlyStatistics::getItemId, projectId)
                .eq(CrewMonthlyStatistics::getStatisticsMonth, month)
                .and(wrapper ->
                        wrapper.gt(CrewMonthlyStatistics::getLeavePosCount, 0).or()
                                .gt(CrewMonthlyStatistics::getSlowDownCount, 0).or()
                                .gt(CrewMonthlyStatistics::getLeaveSiteCount, 0).or()
                                .gt(CrewMonthlyStatistics::getGadPosCount, 0).or()
                                .gt(CrewMonthlyStatistics::getGatherPosCount, 0)
                );
        List<CrewMonthlyStatistics> list = iCrewMonthlyStatisticsService.list(queryWrapper);
        return BeanCopyUtil.copyList(list, CrewBehaviorDetail.class);
    }

    /**
     *
     * @param projectId
     * @param yearMonth
     * @return
     */
    public List<DailyCrewBehavior> getMonthlyCrewBehaviorCountDetailList(String projectId, String yearMonth) {
        List<DailyCrewBehavior> dailyCrewBehaviorList = new ArrayList<>();
        Map<String, LocalDate> dateRange = getDateRange(yearMonth);
        LocalDate firstDay = dateRange.get(FIRST_DAY);
        LocalDate lastDay = dateRange.get(LAST_DAY);
        List<DailyStatistics> list = iDailyStatisticsService.getDailyStatisticsList(projectId, firstDay, lastDay);
        if(CollUtil.isNotEmpty(list)) {
            // 排序
            LinkedHashMap<String, List<DailyStatistics>> collectMap = list.stream()
                    .sorted(Comparator.comparing(DailyStatistics::getDate).reversed())
                    .collect(Collectors.groupingBy(DailyStatistics::getStatisticsDate, LinkedHashMap::new, Collectors.toList()));
            for (Map.Entry<String, List<DailyStatistics>> entry : collectMap.entrySet()) {
                List<DailyStatistics> dailyStatisticsList = entry.getValue();
                DailyStatistics dailyStatistics = dailyStatisticsList.get(0);
                dailyCrewBehaviorList.add(BeanCopyUtil.copyProperties(dailyStatistics, DailyCrewBehavior.class));
            }
        }
        return dailyCrewBehaviorList;
    }

    /**
     * 按日查询作业覆盖。查询风险区域、不及格巡检数量、巡检工单总数指标
     * @param projectId
     * @param date
     * @return
     */
    public JobCoverageCount getDailyJobCoverageCount(String projectId, String date) {
        DailyStatistics dailyStatistics = getDailyStatistics(projectId, date);
        if(dailyStatistics == null) {
            return null;
        }
        return BeanCopyUtil.copyProperties(dailyStatistics, JobCoverageCount.class);
    }

    /**
     * 按月查询作业覆盖。查询风险区域、不及格巡检数量、巡检工单总数指标
     * @param projectId
     * @param month
     * @return
     */
    public JobCoverageCount getMonthlyJobCoverageCount(String projectId, String month) {
        MonthlyStatistics monthlyStatistics = getMonthlyStatistics(projectId, month);
        if(monthlyStatistics == null) {
            return null;
        }
        return BeanCopyUtil.copyProperties(monthlyStatistics, JobCoverageCount.class);
    }

    /**
     * 作业覆盖-月度数据。显示每日风险区域、不及格巡检数量
     * @param projectId
     * @param yearMonth
     * @return
     */
    public List<JobCoverageCount> getMonthlyJobCoverageDetail(String projectId, String yearMonth) {
        List<JobCoverageCount> jobCoverageCountList = new ArrayList<>();
        Map<String, LocalDate> dateRange = getDateRange(yearMonth);
        LocalDate firstDay = dateRange.get(FIRST_DAY);
        LocalDate lastDay = dateRange.get(LAST_DAY);
        List<DailyStatistics> list = iDailyStatisticsService.getDailyStatisticsList(projectId, firstDay, lastDay);
        if(CollUtil.isNotEmpty(list)) {
            // 排序
            LinkedHashMap<String, List<DailyStatistics>> collectMap = list.stream()
                    .sorted(Comparator.comparing(DailyStatistics::getDate).reversed())
                    .collect(Collectors.groupingBy(DailyStatistics::getStatisticsDate, LinkedHashMap::new, Collectors.toList()));
            for (Map.Entry<String, List<DailyStatistics>> entry : collectMap.entrySet()) {
                List<DailyStatistics> dailyStatisticsList = entry.getValue();
                DailyStatistics dailyStatistics = dailyStatisticsList.get(0);
                jobCoverageCountList.add(BeanCopyUtil.copyProperties(dailyStatistics, JobCoverageCount.class));
            }
        }
        return jobCoverageCountList;
    }


    /**
     * 作业覆盖-月度数据(空间)。显示每日空间风险区域数量
     * @param projectId
     * @param yearMonth
     * @return
     */
    public List<SpaceRiskAreaCount> getMonthlySpaceRiskAreaCountDetail(String projectId, String yearMonth) {
        List<SpaceRiskAreaCount> spaceRiskAreaCountList = new ArrayList<>();
        Map<String, LocalDate> dateRange = getDateRange(yearMonth);
        LocalDate firstDay = dateRange.get(FIRST_DAY);
        LocalDate lastDay = dateRange.get(LAST_DAY);
        List<SpaceDailyStatistics> list = getDailySpaceStatisticsList(projectId, firstDay, lastDay);
        if(CollUtil.isNotEmpty(list)) {
            // 排序
            LinkedHashMap<String, List<SpaceDailyStatistics>> collectMap = list.stream()
                    .sorted(Comparator.comparing(SpaceDailyStatistics::getDate).reversed())
                    .collect(Collectors.groupingBy(SpaceDailyStatistics::getStatisticsDate, LinkedHashMap::new, Collectors.toList()));
            for (Map.Entry<String, List<SpaceDailyStatistics>> entry : collectMap.entrySet()) {
                List<SpaceDailyStatistics> spaceDailyStatisticsList = entry.getValue();
                spaceRiskAreaCountList.addAll(BeanCopyUtil.copyList(spaceDailyStatisticsList, SpaceRiskAreaCount.class));
            }
        }
        return spaceRiskAreaCountList;
    }


    /**
     * 机器人-今日日清工单、未达标机器人数量
     * @param projectId
     * @param date
     * @return
     */
    public RobotCount getDailyRobotCount(String projectId, String date) {
        DailyStatistics dailyStatistics = getDailyStatistics(projectId, date);
        if(dailyStatistics == null) {
            return null;
        }
        return BeanCopyUtil.copyProperties(dailyStatistics, RobotCount.class);
    }

    /**
     * 机器人-月度日清工单、未达标机器人数量
     * @param projectId
     * @param month
     * @return
     */
    public RobotCount getMonthlyRobotCount(String projectId, String month) {
        MonthlyStatistics monthlyStatistics = getMonthlyStatistics(projectId, month);
        if(monthlyStatistics == null) {
            return null;
        }
        return BeanCopyUtil.copyProperties(monthlyStatistics, RobotCount.class);
    }

    /**
     * 机器人-月度数据。显示每日日清工单、未达标机器人数量
     * @param projectId
     * @param yearMonth
     * @return
     */
    public List<RobotCount> getMonthlyRobotCountDetail(String projectId, String yearMonth) {
        List<RobotCount> robotCountList = new ArrayList<>();
        Map<String, LocalDate> dateRange = getDateRange(yearMonth);
        LocalDate firstDay = dateRange.get(FIRST_DAY);
        LocalDate lastDay = dateRange.get(LAST_DAY);
        List<DailyStatistics> list = iDailyStatisticsService.getDailyStatisticsList(projectId, firstDay, lastDay);
        if(CollUtil.isNotEmpty(list)) {
            // 排序
            LinkedHashMap<String, List<DailyStatistics>> collectMap = list.stream()
                    .sorted(Comparator.comparing(DailyStatistics::getDate).reversed())
                    .collect(Collectors.groupingBy(DailyStatistics::getStatisticsDate, LinkedHashMap::new, Collectors.toList()));
            for (Map.Entry<String, List<DailyStatistics>> entry : collectMap.entrySet()) {
                List<DailyStatistics> dailyStatisticsList = entry.getValue();
                DailyStatistics dailyStatistics = dailyStatisticsList.get(0);
                robotCountList.add(BeanCopyUtil.copyProperties(dailyStatistics, RobotCount.class));
            }
        }
        return robotCountList;
    }


    /**
     * 机器人-月度数据（机器人清洁面积）。显示机器人每日清洁面积
     * @param projectId
     * @param yearMonth
     * @return
     */
    public List<RobotCleanArea> getMonthlyRobotCleanArea(String projectId, String yearMonth) {
        List<RobotCleanArea> robotClearAreaList = new ArrayList<>();
        Map<String, LocalDate> dateRange = getDateRange(yearMonth);
        LocalDate firstDay = dateRange.get(FIRST_DAY);
        LocalDate lastDay = dateRange.get(LAST_DAY);
        List<RobotDailyStatistics> list = getRobotDailyStatisticsList(projectId, firstDay, lastDay);
        if(CollUtil.isNotEmpty(list)) {
            // 排序
            LinkedHashMap<String, List<RobotDailyStatistics>> collectMap = list.stream()
                    .sorted(Comparator.comparing(RobotDailyStatistics::getDate).reversed())
                    .collect(Collectors.groupingBy(RobotDailyStatistics::getStatisticsDate, LinkedHashMap::new, Collectors.toList()));
            for (Map.Entry<String, List<RobotDailyStatistics>> entry : collectMap.entrySet()) {
                List<RobotDailyStatistics> robotDailyStatisticsList = entry.getValue();
                robotClearAreaList.addAll(BeanCopyUtil.copyList(robotDailyStatisticsList, RobotCleanArea.class));
            }
        }
        return robotClearAreaList;
    }



    private ProjectModuleMonitorAlert  getProjectModuleMonitorAlert(String projectId, String date, String moduleCode){
        ProjectModuleMonitorAlert projectModuleMonitorAlert = null;
        //查询项目配置的已启用出勤模块总结指标，并按优先级从高到低排序
        List<ProjectMonitorIndicatorConfig> projectMonitorIndicatorConfigList = iProjectMonitorIndicatorService.getProjectMonitorIndicatorConfig(moduleCode, projectId);
        if(CollUtil.isNotEmpty(projectMonitorIndicatorConfigList)) {
            projectModuleMonitorAlert = new ProjectModuleMonitorAlert();
            projectModuleMonitorAlert.setModuleCode(moduleCode);
            List<ProjectModuleMonitorAlert.AlertMsg> alertMsgList = new ArrayList<>();
            for(ProjectMonitorIndicatorConfig config : projectMonitorIndicatorConfigList) {
                String moduleName = config.getModuleName();
                projectModuleMonitorAlert.setModuleName(moduleName);
                // 创建提醒信息对象
                ProjectModuleMonitorAlert.AlertMsg alertMsg = getAlertMsg(projectId, date, config);
                if(alertMsg != null){
                    alertMsgList.add(alertMsg);
                }
            }
            if(CollUtil.isNotEmpty(alertMsgList)) {
                projectModuleMonitorAlert.setAlertMsgList(alertMsgList);
            }
        }
        return projectModuleMonitorAlert;

    }

    private ProjectModuleMonitorAlert.AlertMsg getAlertMsg(String projectId, String date, ProjectMonitorIndicatorConfig config) {
        switch (config.getIndicatorNo()){
            case CQ001:
                return getCQ001AlertMsg(projectId, date, config);
            case CQ002:
                return getCQ002AlertMsg(projectId, date, config);
            case CQ003:
                return getCQ003AlertMsg(projectId, date, config);
            case JQ001:
                return getJQ001AlertMsg(projectId, date, config);
            case PZ001:
                return getPZ001AlertMsg(projectId, date, config);
            case PZ002:
                return getPZ002AlertMsg(projectId, date, config);
            case RY001:
                return getRY001AlertMsg(projectId, date, config);
            case RY002:
                return getRY002AlertMsg(projectId, date, config);
            case RY003:
                return getRY003AlertMsg(projectId, date, config);
            case RY004:
                return getRY004AlertMsg(projectId, date, config);
            default:
                return null;
        }
    }

    /**
     * 指标描述：怠工（怠工告警次数）
     * 数据来源：t_item_daily_statistics、t_item_crew_daily_statistics
     * 监控指标：怠工次数（slow_down_count）
     * 提示信息：怠工共X次，「N」怠工次数较多为Y次，请加强该员工管理。、「N」共怠工Y次。
     * 变量取值：
     * X：t_item_daily_statistics中今日怠工次数slow_down_count
     * N：t_item_crew_daily_statistics中今日怠工次数slow_down_count最大值的人员的姓名crew_name
     * Y：t_item_crew_daily_statistics中今日怠工次数slow_down_count最大值
     * @param projectId
     * @param dateStr
     * @param config
     * @return
     */
    private ProjectModuleMonitorAlert.AlertMsg getRY004AlertMsg(String projectId, String dateStr, ProjectMonitorIndicatorConfig config) {
        int x = 0;
        DailyStatistics dailyStatistics = getDailyStatistics(projectId, dateStr);
        if(dailyStatistics != null){
            x = dailyStatistics.getSlowDownCount();
        }
        // 判断指标是符满足设定的阀值
        if(CharSequenceUtil.isNotBlank(config.getComparisonValue())
                && config.getComparisonOperator().compare(x, Double.parseDouble(config.getComparisonValue()))){
            int y = iCrewDailyStatisticsService.getMaxSlowDownCount(projectId, dateStr);
            List<String> nList = new ArrayList<>();
            if(y > 0) {
                LambdaQueryWrapper<CrewDailyStatistics> queryWrapper = new LambdaQueryWrapper<CrewDailyStatistics>()
                        .eq(CrewDailyStatistics::getItemId, projectId)
                        .eq(CrewDailyStatistics::getStatisticsDate, dateStr)
                        .eq(CrewDailyStatistics::getSlowDownCount, y);
                List<CrewDailyStatistics> crewDailyStatisticsList = iCrewDailyStatisticsService.list(queryWrapper);
                if(CollUtil.isNotEmpty(crewDailyStatisticsList)){
                    for(CrewDailyStatistics crewDailyStatistics : crewDailyStatisticsList){
                        nList.add(crewDailyStatistics.getCrewName());
                    }
                }
            }
            String n = getNameStr(nList);
            if(x > 0 || y > 0 || CharSequenceUtil.isNotBlank(n)){
                return createAlertMsg(config, String.valueOf(x), String.valueOf(y), n);
            }
        }
        return null;
    }

    private static @NotNull String getNameStr(List<String> nList) {
        StringBuilder sb = new StringBuilder();
        if(CollUtil.isNotEmpty(nList)){
            for (String n : nList){
                sb.append(n).append("、");
            }
        }
        String n = sb.toString();
        return removeLastChar(n);
    }

    public static String removeLastChar(String input) {
        if (CharSequenceUtil.isNotBlank(input)) {
            return input.substring(0, input.length() - 1); // 去掉最后一个字符
        }
        return input; // 如果字符串为空，直接返回
    }



    /**
     * 指标描述：聚岗（聚岗告警次数）
     * 数据来源：t_item_daily_statistics
     * 监控指标：聚岗次数（gather_pos_count）
     * 提示信息：聚岗X次，近7日聚岗Y次，请加强现场管理。、聚岗X次，近7日聚岗Y次。
     * 变量取值：
     * X：今日gather_pos_count
     * Y：近7日gather_pos_count之和
     * @param projectId
     * @param dateStr
     * @param config
     * @return
     */
    private ProjectModuleMonitorAlert.AlertMsg getRY003AlertMsg(String projectId, String dateStr, ProjectMonitorIndicatorConfig config) {
        int x = 0;
        int y = 0;
        DailyStatistics dailyStatistics = getDailyStatistics(projectId, dateStr);
        if(dailyStatistics != null) {
            x = dailyStatistics.getGatherPosCount();
        }
        // 判断指标是符满足设定的阀值
        if(CharSequenceUtil.isNotBlank(config.getComparisonValue())
                && config.getComparisonOperator().compare(x, Double.parseDouble(config.getComparisonValue()))){
            LocalDate date = LocalDate.parse(dateStr, PURE_DATE_FORMATTER);
            LocalDate startDate = date.plusDays(-6);
            List<DailyStatistics> dailyStatisticsList = iDailyStatisticsService.getDailyStatisticsList(projectId, startDate, date);
            if(CollUtil.isNotEmpty(dailyStatisticsList)) {
                y = dailyStatisticsList.stream().mapToInt(DailyStatistics::getGatherPosCount).sum();
            }
            if(x > 0 || y > 0) {
                return createAlertMsg(config, String.valueOf(x), String.valueOf(y));
            }
        }

        return null;
    }

    /**
     * 指标描述：脱岗（脱岗告警次数）
     * 数据来源：t_item_daily_statistics
     * 监控指标：脱岗次数(leave_pos_count)
     * 提示信息：脱岗X次，近7日脱岗Y次，请加强现场管理。、脱岗X次，近7日脱岗Y次。
     * 变量取值：
     * X：今日leave_pos_count
     * Y：近7日leave_pos_count之和
     * @param projectId
     * @param dateStr
     * @param config
     * @return
     */
    private ProjectModuleMonitorAlert.AlertMsg getRY002AlertMsg(String projectId, String dateStr, ProjectMonitorIndicatorConfig config) {
        int x = 0;
        int y = 0;
        DailyStatistics dailyStatistics = getDailyStatistics(projectId, dateStr);
        if(dailyStatistics != null) {
            x = dailyStatistics.getLeavePosCount();
        }
        // 判断指标是符满足设定的阀值
        if(CharSequenceUtil.isNotBlank(config.getComparisonValue())
                && config.getComparisonOperator().compare(x, Double.parseDouble(config.getComparisonValue()))){
            LocalDate date = LocalDate.parse(dateStr, PURE_DATE_FORMATTER);
            LocalDate startDate = date.plusDays(-6);
            List<DailyStatistics> dailyStatisticsList = iDailyStatisticsService.getDailyStatisticsList(projectId, startDate, date);
            if(CollUtil.isNotEmpty(dailyStatisticsList)) {
                y = dailyStatisticsList.stream().mapToInt(DailyStatistics::getLeavePosCount).sum();
            }
            if(x > 0 || y > 0) {
                return createAlertMsg(config, String.valueOf(x), String.valueOf(y));
            }
        }
        return null;
    }




    /**
     * 指标描述：离场（离场告警次数）
     * 数据来源：t_item_daily_statistics
     * 监控指标：离场次数（leave_site_count）
     * 提示信息：离场共发生X起，其中「N」共离场Y小时，请核实其原因。\「N」共离场Y小时。
     * 变量取值：
     * X：leave_site_count
     * N：t_item_crew_daily_statistics中今日离场时长leave_site_dura最大值的人员的姓名crew_name
     * Y：t_item_crew_daily_statistics中今日离场时长leave_site_dura最大值
     * @param projectId
     * @param dateStr
     * @param config
     * @return
     */
    private ProjectModuleMonitorAlert.AlertMsg getRY001AlertMsg(String projectId, String dateStr, ProjectMonitorIndicatorConfig config) {
        int x = 0;
        DailyStatistics dailyStatistics = getDailyStatistics(projectId, dateStr);
        if(dailyStatistics != null){
            x = dailyStatistics.getLeaveSiteCount();
        }
        // 判断指标是符满足设定的阀值
        if(CharSequenceUtil.isNotBlank(config.getComparisonValue())
                && config.getComparisonOperator().compare(x, Double.parseDouble(config.getComparisonValue()))){
            int y = iCrewDailyStatisticsService.getMaxLeaveSiteDura(projectId, dateStr);
            List<String> nList = new ArrayList<>();
            if(y > 0){
                LambdaQueryWrapper<CrewDailyStatistics> queryWrapper = new LambdaQueryWrapper<CrewDailyStatistics>()
                        .eq(CrewDailyStatistics::getItemId, projectId)
                        .eq(CrewDailyStatistics::getStatisticsDate, dateStr)
                        .eq(CrewDailyStatistics::getLeaveSiteDura, y);
                List<CrewDailyStatistics> crewDailyStatisticsList = iCrewDailyStatisticsService.list(queryWrapper);
                if(CollUtil.isNotEmpty(crewDailyStatisticsList)){
                    for(CrewDailyStatistics crewDailyStatistics : crewDailyStatisticsList){
                        nList.add(crewDailyStatistics.getCrewName());
                    }
                }
            }
            String n = getNameStr(nList);
            if(x > 0 || y > 0 || CharSequenceUtil.isNotBlank(n)){
                // 将y的单位由秒转为小时。
               return createAlertMsg(config, String.valueOf(x), String.valueOf(NumberUtil.div(y,3600, 2)), n);
            }
        }
        return null;
    }


    /**
     * 指标描述：巡检工单（不及格工单数）
     * 数据来源：t_item_daily_statistics
     * 监控指标：巡检工单数量（inspect_order_count）
     * 提示信息：巡检工单不合格点位数X个，占比P，需加强管理人员管控。、巡检工单不合格点位数X个，占比P
     * 变量取值：
     * X：今日inspect_order_belowstd_count
     * P：今日inspect_order_belowstd_count/今日inspect_order_count
     * @param projectId
     * @param dateStr
     * @param config
     * @return
     */
    private ProjectModuleMonitorAlert.AlertMsg getPZ002AlertMsg(String projectId, String dateStr, ProjectMonitorIndicatorConfig config) {
        DailyStatistics dailyStatistics = getDailyStatistics(projectId, dateStr);
        if(dailyStatistics != null){
            String p = null;
            int x = dailyStatistics.getInspectOrderBelowstdCount();
            int inspectOrderCount = dailyStatistics.getInspectOrderCount();
            // 判断指标是符满足设定的阀值
            if(CharSequenceUtil.isNotBlank(config.getComparisonValue())
                    && config.getComparisonOperator().compare(x, Double.parseDouble(config.getComparisonValue()))){
                if(x > 0){
                    p = NumberUtil.formatPercent(NumberUtil.div(x, inspectOrderCount, 2), 2) ;
                }
                if(x > 0 || p != null){
                    return createAlertMsgWithP(config, String.valueOf(x), p);
                }
            }
        }
        return null;
    }

    /**
     * 指标描述：风险区（风险告警次数）
     * 数据来源：t_item_daily_statistics、t_item_space_daily_statistics
     * 监控指标：风险区数量（risk_area_count）
     * 提示信息：风险区共X次，「N」发生频次较高为Y次，需加强对应岗位人员管理。、「N」发生覆盖风险Y次。
     * 变量取值：
     * X：t_item_daily_statistics中今日风险区数量risk_area_count
     * N：t_item_space_daily_statistics中今日风险区数量risk_area_count最大值的地区的名称space_name
     * Y：t_item_space_daily_statistics今日风险区数量risk_area_count最大值
     * @param projectId
     * @param dateStr
     * @param config
     * @return
     */
    private ProjectModuleMonitorAlert.AlertMsg getPZ001AlertMsg(String projectId, String dateStr, ProjectMonitorIndicatorConfig config) {
        int x = 0;
        DailyStatistics dailyStatistics = getDailyStatistics(projectId, dateStr);
        if(dailyStatistics != null) {
            x = dailyStatistics.getRiskAreaCount();
        }
        // 判断指标是符满足设定的阀值
        if(CharSequenceUtil.isNotBlank(config.getComparisonValue())
                && config.getComparisonOperator().compare(x, Double.parseDouble(config.getComparisonValue()))){
            List<String> nList = new ArrayList<>();
            int y = iSpaceDailyStatisticsService.getMaxRiskAreaCount(projectId, dateStr);
            if(y > 0){
                LambdaQueryWrapper<SpaceDailyStatistics> queryWrapper = new LambdaQueryWrapper<SpaceDailyStatistics>()
                        .eq(SpaceDailyStatistics::getItemId, projectId)
                        .eq(SpaceDailyStatistics::getRiskAreaCount, y)
                        .eq(SpaceDailyStatistics::getStatisticsDate, dateStr);
                List<SpaceDailyStatistics> spaceDailyStatisticsList = iSpaceDailyStatisticsService.list(queryWrapper);
                if(CollUtil.isNotEmpty(spaceDailyStatisticsList)) {
                    for(SpaceDailyStatistics spaceDailyStatistics : spaceDailyStatisticsList){
                        nList.add(spaceDailyStatistics.getSpaceName());
                    }
                }
            }
            String n = getNameStr(nList);
            if(x > 0 || y > 0 || CharSequenceUtil.isNotBlank(n)){
                return createAlertMsg(config, String.valueOf(x), String.valueOf(y), n);
            }
        }

        return null;
    }


    /**
     * 指标描述：未达标台数
     * 数据来源：t_item_daily_statistics
     * 监控指标：未达标台数 robot_run_downstd_count
     * 提示信息：未达标机器人X台。、未达标X台，近30日未达标Y台，需加强机器人运行管理。
     * 变量取值：
     * X：今日robot_run_downstd_count
     * Y：近30日robot_run_downstd_count之和
     *
     * @param projectId
     * @param dateStr
     * @param config
     * @return
     */
    private ProjectModuleMonitorAlert.AlertMsg getJQ001AlertMsg(String projectId, String dateStr, ProjectMonitorIndicatorConfig config) {
        int x = 0;
        int y = 0;
        DailyStatistics dailyStatistics = getDailyStatistics(projectId, dateStr);
        if(dailyStatistics != null) {
            x = dailyStatistics.getRobotRunDownstdCount();
        }
        // 判断指标是符满足设定的阀值
        if(CharSequenceUtil.isNotBlank(config.getComparisonValue())
                && config.getComparisonOperator().compare(x, Double.parseDouble(config.getComparisonValue()))){
            LocalDate date = LocalDate.parse(dateStr, PURE_DATE_FORMATTER);
            LocalDate startDate = date.plusDays(-29);
            List<DailyStatistics> list = iDailyStatisticsService.getDailyStatisticsList(projectId, startDate, date);
            if(CollUtil.isNotEmpty(list)) {
                y = list.stream().mapToInt(DailyStatistics::getRobotRunDownstdCount).sum();
            }
            if(x > 0 || y > 0) {
                return createAlertMsg(config, String.valueOf(x), String.valueOf(y));
            }
        }
        return null;
    }



    /**
     * 指标描述：出勤-工牌配置率（配置人数/打卡人数）
     * 数据来源：t_item_daily_statistics
     * 监控指标：工牌配置率=工牌配置人数(card_allocate_crew_count)/打卡出勤人数(clock_attendance_crew_count)
     * 提示信息：未配工牌人数X人。
     * 变量取值：
     *  X：今日打卡出勤人数(clock_attendance_crew_count)-今日工牌配置人数(card_allocate_crew_count)
     * @param projectId
     * @param dateStr
     * @param config
     * @return
     */
    private ProjectModuleMonitorAlert.AlertMsg getCQ003AlertMsg(String projectId, String dateStr, ProjectMonitorIndicatorConfig config) {
        int x = 0;
        double rate = 0.0;
        DailyStatistics dailyStatistics = getDailyStatistics(projectId, dateStr);
        if(dailyStatistics != null ) {
            int clockAttendanceCrewCount = 0;
            int cardAllocateCrewCrewCount = 0;
            if(dailyStatistics.getClockAttendanceCrewCount() != null){
                clockAttendanceCrewCount = dailyStatistics.getClockAttendanceCrewCount();
            }
            if(dailyStatistics.getCardAllocateCrewCount() != null){
                cardAllocateCrewCrewCount = dailyStatistics.getCardAllocateCrewCount();
            }
            if(clockAttendanceCrewCount > 0){
                rate = NumberUtil.div(cardAllocateCrewCrewCount, clockAttendanceCrewCount, 2);
            }
            x =  clockAttendanceCrewCount - cardAllocateCrewCrewCount;
        }
        // 判断指标是符满足设定的阀值
        if(CharSequenceUtil.isNotBlank(config.getComparisonValue())
                && config.getComparisonOperator().compare(rate, Double.parseDouble(config.getComparisonValue()))
                && x > 0 ){
            return createAlertMsg(config, String.valueOf(x));
        }
        return null;
    }

    /**
     * 指标描述：出勤-超龄（超规定年龄人数）
     * 数据来源：t_item_daily_statistics
     * 监控指标：今日overage_crew_count
     * 提示信息：在职超龄X人。、在职超龄X人，需强化超龄风险管控
     * 变量取值：
     * X：今日overage_crew_count
     * @param projectId
     * @param dateStr
     * @param config
     * @return
     */
    private ProjectModuleMonitorAlert.AlertMsg getCQ002AlertMsg(String projectId, String dateStr, ProjectMonitorIndicatorConfig config) {
        int x = 0;
        DailyStatistics dailyStatistics = getDailyStatistics(projectId, dateStr);
        if(dailyStatistics != null) {
            x = dailyStatistics.getOverageCrewCount();
        }
        // 判断指标是符满足设定的阀值
        if(CharSequenceUtil.isNotBlank(config.getComparisonValue())
                && config.getComparisonOperator().compare(x, Double.parseDouble(config.getComparisonValue()))
                && x > 0 ){
            return createAlertMsg(config, String.valueOf(x));
        }
        return null;
    }


    /**
     * 指标描述：出勤-缺岗（合同出勤-出勤）
     * 数据来源：t_item_daily_statistics
     * 监控指标：今日vacant_pos_crew_count
     * 提示信息：缺岗X人，近30日缺岗天数Y天。、缺岗X人，近30日缺岗天数Y天。
     * 变量取值：
     * X：今日vacant_pos_crew_count
     * Y：统计近30天vacant_pos_crew_count>0的数量
     * @param projectId
     * @param dateStr
     * @param config
     * @return
     */
    private ProjectModuleMonitorAlert.AlertMsg getCQ001AlertMsg(String projectId, String dateStr, ProjectMonitorIndicatorConfig config) {
        int x = 0;
        DailyStatistics dailyStatistics = getDailyStatistics(projectId, dateStr);
        if(dailyStatistics != null) {
            x = dailyStatistics.getVacantPosCrewCount();
        }
        // 判断指标是符满足设定的阀值
        if(CharSequenceUtil.isNotBlank(config.getComparisonValue())
                && config.getComparisonOperator().compare(x, Double.parseDouble(config.getComparisonValue()))){
            LocalDate date = LocalDate.parse(dateStr, PURE_DATE_FORMATTER);
            LocalDate startDate = date.plusDays(-30);
            LambdaQueryWrapper<DailyStatistics> queryWrapper = new LambdaQueryWrapper<DailyStatistics>()
                    .ge(DailyStatistics::getStatisticsDate, startDate.format(PURE_DATE_FORMATTER))
                    .le(DailyStatistics::getStatisticsDate, dateStr)
                    .eq(DailyStatistics::getItemId, projectId)
                    .gt(DailyStatistics::getVacantPosCrewCount, 0);
            long y = iDailyStatisticsService.count(queryWrapper);
            if(x > 0 || y > 0) {
                return createAlertMsg(config, String.valueOf(x), String.valueOf(y));
            }
        }
        return null;
    }

    private ProjectModuleMonitorAlert.AlertMsg createAlertMsgWithP(ProjectMonitorIndicatorConfig config, String x, String p){
        return createAlertMsg(config, x, "0", null, p);
    }

    private ProjectModuleMonitorAlert.AlertMsg createAlertMsg(ProjectMonitorIndicatorConfig config, String x){
        return createAlertMsg(config, x, "0", null, null);
    }

    private ProjectModuleMonitorAlert.AlertMsg createAlertMsg(ProjectMonitorIndicatorConfig config, String x, String y){
        return createAlertMsg(config, x, y, null, null);
    }

    private ProjectModuleMonitorAlert.AlertMsg createAlertMsg(ProjectMonitorIndicatorConfig config, String x, String y, String n){
        return createAlertMsg(config, x, y, n, null);
    }

    private ProjectModuleMonitorAlert.AlertMsg createAlertMsg(ProjectMonitorIndicatorConfig config, String x, String y, String n, String p) {
        ProjectModuleMonitorAlert.AlertMsg alertMsg = new ProjectModuleMonitorAlert.AlertMsg();
        alertMsg.setPriority(config.getPriority());
        String suggestTips = config.getSuggestTips();
        String summaryTips = config.getSummaryTips();

        suggestTips = replaceParam(x, y, n, p, suggestTips);
        summaryTips = replaceParam(x, y, n, p, summaryTips);
        if(CharSequenceUtil.isNotBlank(suggestTips)){
            alertMsg.setSuggestTips(suggestTips);
        }
        if(CharSequenceUtil.isNotBlank(summaryTips)){
            alertMsg.setSummaryTips(summaryTips);
        }
        return alertMsg;
    }


    private String replaceParam(String x, String y, String n, String p, String tipsWithParam) {
        if(tipsWithParam != null) {
            if(tipsWithParam.contains("X") || tipsWithParam.contains("x")){
                tipsWithParam = tipsWithParam.replace("X", String.valueOf(x)).replace("x", String.valueOf(x));
            }
            if(tipsWithParam.contains("Y") || tipsWithParam.contains("y")){
                tipsWithParam = tipsWithParam.replace("Y", String.valueOf(y)).replace("y", String.valueOf(y));
            }
            if((tipsWithParam.contains("N") || tipsWithParam.contains("n"))){
                tipsWithParam = tipsWithParam.replace("N", n).replace("n", n);
            }
            if((tipsWithParam.contains("P") || tipsWithParam.contains("p")) && CharSequenceUtil.isNotBlank(p)){
                tipsWithParam = tipsWithParam.replace("P", p).replace("p", p);
            }
            return tipsWithParam;
        }
        return null;
    }

    private Map<String, LocalDate> getDateRange(String yearMonth){
        Map<String, LocalDate> map = new HashMap<>();
        int year = Integer.parseInt(yearMonth.substring(0, 4));
        int month = Integer.parseInt(yearMonth.substring(4, 6));
        // 获取该月的第一天
        LocalDate firstDay = LocalDate.of(year, month, 1);
        LocalDate lastDay;
        LocalDate today = LocalDate.now();
        if (firstDay.isBefore(today.withDayOfMonth(1))) {
            // 过去的月份，获取该月的最后一天
            lastDay = firstDay.withDayOfMonth(firstDay.lengthOfMonth());
        } else if (firstDay.getYear() == today.getYear() && firstDay.getMonthValue() == today.getMonthValue()) {
            // 当前月份，获取当前日期
            lastDay = today;
        } else {
            throw new CustomException("月份不正确");
        }
        map.put(FIRST_DAY, firstDay);
        map.put(LAST_DAY, lastDay);
        return map;
    }



    /**
     * 月度作业品质扣款明细
     * @param monthlyStatistics
     * @return
     */
    private DeductionDetail.QualityIndicator getMonthlyQualityDeductionDetail(MonthlyStatistics monthlyStatistics) {
        DeductionDetail.QualityIndicator qualityDeductionDetail = new DeductionDetail.QualityIndicator();
        qualityDeductionDetail.setRiskArea(BigDecimal.valueOf(getDoubleValue(monthlyStatistics.getTotalRiskAreaDeductAmount())));
        qualityDeductionDetail.setInspectCoverRate(BigDecimal.valueOf(getDoubleValue(monthlyStatistics.getTotalInspectCoverDeductAmount())));
        return qualityDeductionDetail;
    }

    /**
     * 月度作业品质数据明细
     * @param monthlyStatistics
     * @return
     */
    private DeductionDetail.QualityIndicator getMonthlyQualityDataDetail(MonthlyStatistics monthlyStatistics) {
        DeductionDetail.QualityIndicator qualityDataDetail = new DeductionDetail.QualityIndicator();
        qualityDataDetail.setRiskArea(new BigDecimal(getIntegerValue(monthlyStatistics.getRiskAreaCount())));
        qualityDataDetail.setInspectCoverRate(BigDecimal.valueOf(getDoubleValue(monthlyStatistics.getInspectCoverRate())));
        return qualityDataDetail;
    }

    /**
     * 月度考勤扣款明细
     * @param monthlyStatistics
     * @return
     */
    private DeductionDetail.AttendanceIndicator getMonthlyAttendanceDeductionDetail(MonthlyStatistics monthlyStatistics) {
        DeductionDetail.AttendanceIndicator attendanceDeductionDetail = new DeductionDetail.AttendanceIndicator();
        attendanceDeductionDetail.setSlowDown(BigDecimal.valueOf(getDoubleValue(monthlyStatistics.getTotalSlowDownDeductAmount())));
        attendanceDeductionDetail.setGatherPos(BigDecimal.valueOf(getDoubleValue(monthlyStatistics.getTotalGatherPosDeductAmount())));
        attendanceDeductionDetail.setLeavePos(BigDecimal.valueOf(getDoubleValue(monthlyStatistics.getTotalLeavePosDeductAmount())));
        attendanceDeductionDetail.setGadPos(BigDecimal.valueOf(getDoubleValue(monthlyStatistics.getTotalGadPosDeductAmount())));
        attendanceDeductionDetail.setLeaveSite(BigDecimal.valueOf(getDoubleValue(monthlyStatistics.getTotalLeaveSiteDeductAmount())));
        attendanceDeductionDetail.setVacantPos(BigDecimal.valueOf(getDoubleValue(monthlyStatistics.getTotalVacantPosDeductAmount())));
        attendanceDeductionDetail.setOverage(BigDecimal.valueOf(getDoubleValue(monthlyStatistics.getTotalOverageDeductAmount())));
        return attendanceDeductionDetail;
    }


    /**
     * 月度考勤数据明细
     * @param monthlyStatistics
     * @return
     */
    private DeductionDetail.AttendanceIndicator getMonthlyAttendanceDataDetail(MonthlyStatistics monthlyStatistics) {
        DeductionDetail.AttendanceIndicator attendanceDataDetail = new DeductionDetail.AttendanceIndicator();
        attendanceDataDetail.setSlowDown(new BigDecimal(getIntegerValue(monthlyStatistics.getSlowDownCount())));
        attendanceDataDetail.setGatherPos(new BigDecimal(getIntegerValue(monthlyStatistics.getGatherPosCount())));
        attendanceDataDetail.setLeavePos(new BigDecimal(getIntegerValue(monthlyStatistics.getLeavePosCount())));
        attendanceDataDetail.setGadPos(new BigDecimal(getIntegerValue(monthlyStatistics.getGadPosCount())));
        attendanceDataDetail.setLeaveSite(new BigDecimal(getIntegerValue(monthlyStatistics.getLeaveSiteCount())));
        attendanceDataDetail.setVacantPos(new BigDecimal(getIntegerValue(monthlyStatistics.getVacantPosCrewCount())));
        attendanceDataDetail.setOverage(new BigDecimal(getIntegerValue(monthlyStatistics.getOverageCrewCount())));
        return attendanceDataDetail;
    }


    /**
     * 每日考勤扣款明细
     * @param dailyStatistics
     * @return
     */
    private DeductionDetail.AttendanceIndicator getDailyAttendanceDeductionDetail(DailyStatistics dailyStatistics) {
        DeductionDetail.AttendanceIndicator attendanceDeductionDetail = new DeductionDetail.AttendanceIndicator();
        attendanceDeductionDetail.setSlowDown(BigDecimal.valueOf(getDoubleValue(dailyStatistics.getTotalSlowDownDeductAmount())));
        attendanceDeductionDetail.setGatherPos(BigDecimal.valueOf(getDoubleValue(dailyStatistics.getTotalGatherPosDeductAmount())));
        attendanceDeductionDetail.setLeavePos(BigDecimal.valueOf(getDoubleValue(dailyStatistics.getTotalLeavePosDeductAmount())));
        attendanceDeductionDetail.setGadPos(BigDecimal.valueOf(getDoubleValue(dailyStatistics.getTotalGadPosDeductAmount())));
        attendanceDeductionDetail.setLeaveSite(BigDecimal.valueOf(getDoubleValue(dailyStatistics.getTotalLeaveSiteDeductAmount())));
        attendanceDeductionDetail.setVacantPos(BigDecimal.valueOf(getDoubleValue(dailyStatistics.getTotalVacantPosDeductAmount())));
        attendanceDeductionDetail.setOverage(BigDecimal.valueOf(getDoubleValue(dailyStatistics.getTotalOverageDeductAmount())));
        return attendanceDeductionDetail;

    }

    /**
     * 每日考勤数据明细
     * @param dailyStatistics
     * @return
     */
    private DeductionDetail.AttendanceIndicator getDailyAttendanceDataDetail(DailyStatistics dailyStatistics) {
        DeductionDetail.AttendanceIndicator attendanceDataDetail = new DeductionDetail.AttendanceIndicator();
        attendanceDataDetail.setSlowDown(new BigDecimal(getIntegerValue(dailyStatistics.getSlowDownCount())));
        attendanceDataDetail.setGatherPos(new BigDecimal(getIntegerValue(dailyStatistics.getGatherPosCount())));
        attendanceDataDetail.setLeavePos(new BigDecimal(getIntegerValue(dailyStatistics.getLeavePosCount())));
        attendanceDataDetail.setGadPos(new BigDecimal(getIntegerValue(dailyStatistics.getGadPosCount())));
        attendanceDataDetail.setLeaveSite(new BigDecimal(getIntegerValue(dailyStatistics.getLeaveSiteCount())));
        attendanceDataDetail.setVacantPos(new BigDecimal(getIntegerValue(dailyStatistics.getVacantPosCrewCount())));
        attendanceDataDetail.setOverage(new BigDecimal(getIntegerValue(dailyStatistics.getOverageCrewCount())));
        return attendanceDataDetail;
    }

    /**
     * 每日作业品质扣款明细
     * @param dailyStatistics
     * @return
     */
    private DeductionDetail.QualityIndicator getDailyQualityDeductionDetail(DailyStatistics dailyStatistics) {
        DeductionDetail.QualityIndicator qualityDeductionDetail = new DeductionDetail.QualityIndicator();
        qualityDeductionDetail.setRiskArea(BigDecimal.valueOf(getDoubleValue(dailyStatistics.getTotalRiskAreaDeductAmount())));
        qualityDeductionDetail.setInspectCoverRate(BigDecimal.valueOf(getDoubleValue(dailyStatistics.getTotalInspectCoverDeductAmount())));
        return qualityDeductionDetail;
    }

    /**
     * 每日作业品质数据明细
     * @param dailyStatistics
     * @return
     */
    private DeductionDetail.QualityIndicator getDailyQualityDataDetail(DailyStatistics dailyStatistics) {
        DeductionDetail.QualityIndicator qualityDataDetail = new DeductionDetail.QualityIndicator();
        qualityDataDetail.setRiskArea(new BigDecimal(getIntegerValue(dailyStatistics.getRiskAreaCount())));
        qualityDataDetail.setInspectCoverRate(BigDecimal.valueOf(getDoubleValue(dailyStatistics.getInspectCoverRate())));
        return qualityDataDetail;
    }



    /**
     *月度作业品质扣款
     * ● total_risk_area_deduct_amount	总风险区扣款金额
     * ● total_inspect_cover_deduct_amount	总巡检覆盖扣款金额
     * @param monthlyStatistics
     * @return
     */
    private Double getMonthlyQualityDeduction(MonthlyStatistics monthlyStatistics) {
        return NumberUtil.add(
                getDoubleValue(monthlyStatistics.getTotalRiskAreaDeductAmount()),
                getDoubleValue(monthlyStatistics.getTotalInspectCoverDeductAmount()));

    }

    /**
     * 每日作业品质扣款
     * ● total_risk_area_deduct_amount	总风险区扣款金额
     * ● total_inspect_cover_deduct_amount	总巡检覆盖扣款金额
     * @param dailyStatistics
     * @return
     */
    private Double getDailyQualityDeduction(DailyStatistics dailyStatistics) {
        return NumberUtil.add(
                getDoubleValue(dailyStatistics.getTotalRiskAreaDeductAmount()),
                getDoubleValue(dailyStatistics.getTotalInspectCoverDeductAmount()));

    }

    /**
     * 月度考勤扣款
     * ● total_slow_down_deduct_amount	总怠工扣款金额
     * ● total_gather_pos_deduct_amount	总聚岗扣款金额
     * ● total_leave_pos_deduct_amount	总脱岗扣款金额
     * ● total_gad_pos_deduct_amount	总串岗扣款金额
     * ● total_leave_site_deduct_amount 总离场扣款金额
     * ● total_vacant_pos_deduct_amount	总缺岗扣款金额
     * ● total_overage_deduct_amount	总超龄扣款金额
     * @param monthlyStatistics
     * @return
     */
    private Double getMonthlyAttendanceDeduction(MonthlyStatistics monthlyStatistics) {
        return NumberUtil.add(
                getDoubleValue(monthlyStatistics.getTotalSlowDownDeductAmount()),
                getDoubleValue(monthlyStatistics.getTotalGatherPosDeductAmount()),
                getDoubleValue(monthlyStatistics.getTotalLeavePosDeductAmount()),
                getDoubleValue(monthlyStatistics.getTotalGadPosDeductAmount()),
                getDoubleValue(monthlyStatistics.getTotalLeaveSiteDeductAmount()),
                getDoubleValue(monthlyStatistics.getTotalVacantPosDeductAmount()),
                getDoubleValue(monthlyStatistics.getTotalOverageDeductAmount()))
                .doubleValue();
    }

    /**
     * 每日考勤扣款
     * ● total_slow_down_deduct_amount	总怠工扣款金额
     * ● total_gather_pos_deduct_amount	总聚岗扣款金额
     * ● total_leave_pos_deduct_amount	总脱岗扣款金额
     * ● total_gad_pos_deduct_amount	总串岗扣款金额
     * ● total_leave_site_deduct_amount 总离场扣款金额
     * ● total_vacant_pos_deduct_amount	总缺岗扣款金额
     * ● total_overage_deduct_amount	总超龄扣款金额
     * @param dailyStatistics
     * @return
     */
    private Double getDailyAttendanceDeduction(DailyStatistics dailyStatistics) {
        return NumberUtil.add(
                        getDoubleValue(dailyStatistics.getTotalSlowDownDeductAmount()),
                        getDoubleValue(dailyStatistics.getTotalGatherPosDeductAmount()),
                        getDoubleValue(dailyStatistics.getTotalLeavePosDeductAmount()),
                        getDoubleValue(dailyStatistics.getTotalGadPosDeductAmount()),
                        getDoubleValue(dailyStatistics.getTotalLeaveSiteDeductAmount()),
                        getDoubleValue(dailyStatistics.getTotalVacantPosDeductAmount()),
                        getDoubleValue(dailyStatistics.getTotalOverageDeductAmount()))
                .doubleValue();
    }



    /**
     * 查询项目每一天的统计数据。
     * @param projectId
     * @param startDate
     * @param endDate
     * @return
     */
    private List<Trend> queryProjectData(String projectId, LocalDate startDate, LocalDate endDate, boolean desc) {
        List<Trend> trendList = new ArrayList<>();
        List<DailyStatistics> list = iDailyStatisticsService.getDailyStatisticsList(projectId, startDate, endDate);
        if(CollUtil.isNotEmpty(list)) {
            // 排序
            LinkedHashMap<String, List<DailyStatistics>> collectMap = null;
            if(desc){
                collectMap =list.stream()
                        .sorted(Comparator.comparing(DailyStatistics::getDate).reversed())
                        .collect(Collectors.groupingBy(DailyStatistics::getStatisticsDate, LinkedHashMap::new, Collectors.toList()));
            }else {
                collectMap =list.stream()
                        .sorted(Comparator.comparing(DailyStatistics::getDate))
                        .collect(Collectors.groupingBy(DailyStatistics::getStatisticsDate, LinkedHashMap::new, Collectors.toList()));
            }
            for (Map.Entry<String, List<DailyStatistics>> entry : collectMap.entrySet()) {
                List<DailyStatistics> dailyStatisticsList = entry.getValue();
                DailyStatistics dailyStatistics = dailyStatisticsList.get(0);
                Trend trend = BeanCopyUtil.copyProperties(dailyStatistics, Trend.class);
                trendList.add(trend);
            }
        }
        return trendList;
    }



    private DailyStatistics getDailyStatistics(String projectId, String dateStr) {
        LambdaQueryWrapper<DailyStatistics> queryWrapper = new LambdaQueryWrapper<DailyStatistics>()
                .eq(DailyStatistics::getItemId, projectId)
                .eq(DailyStatistics::getStatisticsDate, dateStr);
        return iDailyStatisticsService.getOne(queryWrapper, false);
    }

    private MonthlyStatistics getMonthlyStatistics(String projectId, String month) {
        LambdaQueryWrapper<MonthlyStatistics> queryWrapper = new LambdaQueryWrapper<MonthlyStatistics>()
                .eq(MonthlyStatistics::getStatisticsMonth, month)
                .eq(MonthlyStatistics::getItemId, projectId);
        return iMonthlyStatisticsService.getOne(queryWrapper, false);
    }

    private List<SpaceDailyStatistics> getDailySpaceStatisticsList(String projectId, LocalDate startDate, LocalDate endDate) {
        QueryWrapper<SpaceDailyStatistics> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda()
                .ge(SpaceDailyStatistics::getStatisticsDate, startDate.format(PURE_DATE_FORMATTER))
                .le(SpaceDailyStatistics::getStatisticsDate, endDate.format(PURE_DATE_FORMATTER))
                .eq(SpaceDailyStatistics::getItemId, projectId);
        queryWrapper.orderByAsc("STR_TO_DATE('statistics_date', '%Y%m%d')");
        return iSpaceDailyStatisticsService.list(queryWrapper);
    }


    private List<RobotDailyStatistics> getRobotDailyStatisticsList(String projectId, LocalDate startDate, LocalDate endDate) {
        QueryWrapper<RobotDailyStatistics> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda()
                .ge(RobotDailyStatistics::getStatisticsDate, startDate.format(PURE_DATE_FORMATTER))
                .le(RobotDailyStatistics::getStatisticsDate, endDate.format(PURE_DATE_FORMATTER))
                .eq(RobotDailyStatistics::getItemId, projectId);
        queryWrapper.orderByAsc("STR_TO_DATE('statistics_date', '%Y%m%d')");
        return iRobotDailyStatisticsService.list(queryWrapper);
    }



    private double getDoubleValue(Double val){
        if(val == null){
            return 0.0d;
        }
        return val;
    }

    private int getIntegerValue(Integer val){
        if(val == null){
            return 0;
        }
        return val;
    }



}
